﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Drawing.Printing;
using System.IO;
using System.Windows.Forms;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Management;
using Microsoft.Win32;

using Word = Microsoft.Office.Interop.Word;
using Excel = Microsoft.Office.Interop.Excel;
using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using Core = Microsoft.Office.Core;

using iPdf.Properties;

namespace iPdf
{
    class PdfCreator
    {
        #region Fields
        private const string PostScriptCustomForm = "PostScript Custom Page Size";

        private static float widthInPoints, heightInPoints;
        private static string pdfCreatorPrinterName = string.Empty;
        private static string originalPrinterName = string.Empty;

        private static System.Timers.Timer operationTimer ;

        private static bool? installedForCurrentUser = null;

        private struct PdfCreatorItem
        {
            public string PrintFileName;
            public string TargetFileName;

            public PdfCreatorItem(string printFileName, string targetFileName)
            {
                PrintFileName = printFileName;
                TargetFileName = targetFileName;
            }
        }

        #endregion

        #region Properties
        public static bool Running
        {
            get
            {
                return Helper.IsProgramRunning(Global.PdfCreatorName);
            }
        }

        public static bool InUse
        {
            get
            {
                return Global.MainWindow.Busy && Global.CurrentPage.PrinterDependent;
            }
        }


        public static float WidthInPoints
        {
            set
            {
                widthInPoints = value;
            }
        }

        public static float HeightInPoints
        {
            set
            {
                heightInPoints = value;
            }
        }

        public static int WidthInHundredthInches
        {
            get
            {
                return UnitConverter.PointsToHundredthInches(widthInPoints);
            }
        }

        public static int HeightInHundredthInches
        {
            get
            {
                return UnitConverter.PointsToHundredthInches(heightInPoints);
            }
        }

        public static float WidthInMms
        {
            get
            {
                return UnitConverter.PointsToMms(widthInPoints);
            }
        }

        public static float HeightInMms
        {
            get
            {
                return UnitConverter.PointsToMms(heightInPoints);
            }
        }

        private static string PdfCreatorPrinterName
        {
            get
            {
                if (string.IsNullOrEmpty( pdfCreatorPrinterName ))
                {
                    pdfCreatorPrinterName = Global.NoPdfCreator;
                    ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_Printer");

                    foreach (ManagementObject queryObj in searcher.Get())
                    {
                        string driverName = (string)queryObj["DriverName"];
                        if (driverName == Global.PdfCreatorDriverName)
                        {
                            return pdfCreatorPrinterName = (string)queryObj["Name"];
                        }
                    }
                }

                return pdfCreatorPrinterName;
            }
        }

        public static bool InstalledForCurrentUser
        {
            get
            {
                if (installedForCurrentUser == null)
                {
                    RegistryKey hkcu = Registry.CurrentUser;
                    hkcu = hkcu.OpenSubKey(PdfCreatorOption.PdfCreatorProgramRegistry, true);

                    installedForCurrentUser = (bool?) (hkcu != null);
                }

                return (bool)installedForCurrentUser;
            }
        }

        public static string GhostScriptBinDirectory
        {
            get
            {
                RegistryKey hkcu = Registry.CurrentUser;
                hkcu = hkcu.OpenSubKey(PdfCreatorOption.GhostScriptRegistry, true);

                if (hkcu == null)
                {
                    throw new GhostScriptMissing();
                }

                string gsBinDir = (string)hkcu.GetValue(PdfCreatorOption.GhostScriptBinDirRegistry, string.Empty);

                if (!Directory.Exists(gsBinDir))
                {
                    throw new GhostScriptMissing();
                }

                return gsBinDir;
            }
        }

        public static string GhostScriptLibDirectory
        {
            get
            {
                RegistryKey hkcu = Registry.CurrentUser;
                hkcu = hkcu.OpenSubKey(PdfCreatorOption.GhostScriptRegistry, true);

                if (hkcu == null)
                {
                    throw new GhostScriptMissing();
                }

                string gsLibDir = (string)hkcu.GetValue(PdfCreatorOption.GhostScriptLibDirRegistry, string.Empty);

                if (!Directory.Exists(gsLibDir))
                {
                    throw new GhostScriptMissing();
                }

                return gsLibDir;
            }
        }

        public static bool Exists
        {
            get
            {
                return PdfCreatorPrinterName != Global.NoPdfCreator && InstalledForCurrentUser;
            }
        }

        public static bool Initialized
        {
            get;
            protected set;
        }
        #endregion

        #region Ctor

        // Make a static class
        private PdfCreator()
        {
        }

        static PdfCreator()
        {
            operationTimer = new System.Timers.Timer(Global.OperationTimeOut);
            operationTimer.Enabled = false;
            operationTimer.AutoReset = false;

            Initialized = false;
        }

        #endregion

        #region Methods

        public static void Initialize()
        {
            if (!Initialized && PdfCreator.Exists)
            {
                PrinterConfigurator.ChangePrinterPaperSize(PdfCreatorPrinterName, PrinterConfigurator.LetterPaper);
            }

            Initialized = true;
        }

        public static void PrintOut(object doc)
        {
            Activate();
            if (doc is Word.Document)
            {
                PrintOutWordDocument((Word.Document)doc);
            }
            else if (doc is Excel.Worksheet)
            {
                PrintOutExcelSheet((Excel.Worksheet)doc);
            }
            else if (doc is PowerPoint.Presentation)
            {
                PrintOutPowerPointPresentation((PowerPoint.Presentation)doc);
            }
            else
            {
                //bad argument
                throw new InvalidDataException("Invalid argument with unknown type");
            }
        }

        private static void PrintOutWordDocument(Word.Document document)
        {
            bool landscape =
                    document.PageSetup.Orientation == Word.WdOrientation.wdOrientLandscape;

            if (landscape)
            {
                widthInPoints = document.PageSetup.PageHeight;
                heightInPoints = document.PageSetup.PageWidth;
            }
            else
            {
                widthInPoints = document.PageSetup.PageWidth;
                heightInPoints = document.PageSetup.PageHeight;
            }

            SetPapersize();

            object printRange = Word.WdPrintOutRange.wdPrintAllDocument;
            object widthInTwips = UnitConverter.PointsToTwips(widthInPoints);
            object heightInTwips = UnitConverter.PointsToTwips(heightInPoints);

            document.PrintOut(ref  Global.TrueValue, ref Global.FalseValue, ref printRange,
                ref Global.NullObject, ref Global.NullObject, ref Global.NullObject, ref Global.NullObject,
                ref Global.NullObject, ref Global.NullObject, ref Global.NullObject, ref Global.NullObject,
                ref Global.NullObject, ref Global.NullObject, ref Global.NullObject, ref Global.NullObject,
                ref Global.NullObject, ref widthInTwips, ref heightInTwips);
        }

        private static void PrintOutExcelSheet(Excel.Worksheet sheet)
        {
            Helper.Sleep(Global.RestTime);
            SetPapersizeByKind((int)sheet.PageSetup.PaperSize);
            sheet.PrintOut(Global.NullObject, Global.NullObject,
                                    Global.NullObject, Global.FalseValue,
                                    pdfCreatorPrinterName, Global.FalseValue,
                                    Global.FalseValue, Global.NullObject);
        }

        private static void PrintOutPowerPointPresentation(PowerPoint.Presentation presentation)
        {
            widthInPoints = presentation.PageSetup.SlideHeight;
            heightInPoints = presentation.PageSetup.SlideWidth;

            SetPapersize();
            
            presentation.PrintOut(1, presentation.Slides.Count, string.Empty,
                                                1, Microsoft.Office.Core.MsoTriState.msoFalse);
        }

        private static void Activate()
        {
            originalPrinterName = PrinterConfigurator.GetDefaultPrinter();
            PrinterConfigurator.ChangeDefaultPrinter(PdfCreatorPrinterName);
        }

        public static void Deactivate()
        {
            PrinterConfigurator.ChangeDefaultPrinter(originalPrinterName);
        }

        private static PaperSize SelectPapersize()
        {
            PrinterSettings prntSettings = new PrinterSettings();
            prntSettings.PrinterName = pdfCreatorPrinterName;

            foreach (PaperSize ps in prntSettings.PaperSizes)
            {
                if (WidthInHundredthInches == ps.Width &&
                    HeightInHundredthInches == ps.Height)
                {
                    return ps;
                }
            }

            return null;
        }

        public static PaperSize SelectPapersizeByKind(int kind)
        {
            return PrinterConfigurator.SelectPapersizeByKind(pdfCreatorPrinterName, kind);
        }

        private static void AddCustomPaperSize()
        {
            PrinterConfigurator.AddCustomPaperSize(pdfCreatorPrinterName, 
              Global.CustomFormName, WidthInMms, HeightInMms);
        }

        public static void DeleteCustomPaperSize()
        {
            PrinterConfigurator.DeletePaperSize(pdfCreatorPrinterName,
                Global.CustomFormName);
        }

        //set the paper size
        public static void SetPapersize()
        {
            PaperSize papersize = SelectPapersize();
            
            if (papersize == null ||
                papersize.PaperName == PostScriptCustomForm)
            {
                //cause the PostScriptCustomForm doesn't always work, so we 
                //treat it specially
                AddCustomPaperSize();

                //when we add the custom paper size, we also set the default paper size to it.
                //so, we can just return
                return;
            }

            PrinterConfigurator.ChangePrinterPaperSize(pdfCreatorPrinterName, papersize);
        }

        private static void SetPapersizeByKind(int kind)
        {
            PaperSize papersize = SelectPapersizeByKind(kind);
            PrinterConfigurator.ChangePrinterPaperSize(pdfCreatorPrinterName, papersize);
        }


        public static bool WaitForPrintingToFinish(string printFileName)
        {
            //check to see if the printing is finished
            operationTimer.Enabled = true;
            operationTimer.Start();
            while (operationTimer.Enabled == true)
            {
                if (File.Exists(printFileName) && Helper.IsFileInUse(printFileName) == false)
                {
                    operationTimer.Enabled = false;
                    return true;
                }

                Global.CheckUserCancellation();

                //sleep a while for next check
                Helper.Sleep(Global.RetryInterval);
            }

            return false;
        }

        #endregion
    }
}

